/**********************************************************************
 *** *                                                            * ***
 *** *  Copyright (c) 2013 Harmonia Holdings Group LLC            * ***
 *** *                                                            * ***
 *** * Permission is hereby granted to use, modify, copy, and     * ***
 *** * distribute this software and its documentation for any     * ***
 *** * purpose only without profit, provided that the above       * ***
 *** * Copyright Notice appears in all copies and that both the   * ***
 *** * Copyright Notice and this Permission Notice appears in     * ***
 *** * every copy of supporting documentation.  No title to nor   * ***
 *** * ownership of the software is transferred hereby.  The name * ***
 *** * of Harmonia Holdings Group LLC shall not be used in        * ***
 *** * advertising or publicity related to the distribution of    * ***
 *** * the software without specific, written, prior permission.  * ***
 *** * This software is provided as-delivered without expressed   * ***
 *** * or implied warranty.  Harmonia Holdings Group LLC          * ***
 *** * makes no representation about the suitability of this      * ***
 *** * software for any purpose and accepts no responsibility for * ***
 *** * its use.                                                   * ***
 *** *                                                            * ***
 *** ************************************************************** ***
 *** *                                                            * ***
 *** * This program is free software; you can redistribute it     * ***
 *** * and/or modify it under the terms of the GNU General Public * ***
 *** * License as published by the Free Software Foundation;      * ***
 *** * either version 2 of the License, or (at your option) any   * ***
 *** * later version.                                             * ***
 *** *                                                            * ***
 *** * This program is distributed in the hope that it will be    * ***
 *** * useful, but WITHOUT ANY WARRANTY; without even the implied * ***
 *** * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    * ***
 *** * PURPOSE.  See the GNU General Public License for more      * ***
 *** * details.                                                   * ***
 *** *                                                            * ***
 *** * You should have received a copy of the GNU General Public  * ***
 *** * License along with this program; if not, write to the Free * ***
 *** * Software Foundation, Inc., 51 Franklin Street, Fifth       * ***
 *** * Floor, Boston, MA 02110-1301, USA.                         * ***
 *** *                                                            * ***
 *** * For more information: http://www.gnu.org/licenses/gpl.html * ***
 *** *                                                            * ***
 **********************************************************************/
package org.etexascode.interrep.datamodel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.etexascode.interrep.datamodel.interfaces.IDistanceable;
import org.etexascode.interrep.datamodel.utils.UtilsStringOnModel;

/**
 * The model for a lane.
 * @author ablatt
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
@XmlSeeAlso({LaneMovement.class, LaneNode.class})
public class Lane implements Serializable, IDistanceable {
	/** Serial ID. */
	@XmlTransient
	private static final long serialVersionUID = -6255765316875701222L;

	/**
	 * One of two identifiers for the lane type. Designates the lane
	 * as being an inbound lane.
	 */
	@XmlTransient
	public static final String INBOUND = "INBOUND";

	/**
	 * One of two identifiers for the lane type. Designates the lane
	 * as being an outbound lane.
	 */
	@XmlTransient
	public static final String OUTBOUND = "OUTBOUND";


	/** The ID. */
	@XmlElement
	private int laneId;

	/** The approach ID. */
	@XmlElement
	private int approachId;

	/** The list of lane nodes. */
	@XmlElement
	protected List<LaneNode> laneGeomList;

	/** The map of lane movements. */
	@XmlElement
	@XmlJavaTypeAdapter(ManagerAdapter.class)
	private Map<Integer, LaneMovement> laneMovements;

	@XmlElement
	private double speedLimitInMetersPerSecond = 0.0;

	/** The type of lane. */
	@XmlElement
	protected String type; // TODO: ablatt - consider making this an enum type

	/** Empty Constructor. */
	public Lane() {
		laneId = 0;
		approachId = 0;
		laneGeomList = new ArrayList<LaneNode>();
		laneMovements = new HashMap<Integer, LaneMovement>();
		type = "Unset";
	}

	/** Duplicate constructor. */
	public Lane(Lane lane) {
		laneId = lane.laneId;
		approachId = lane.approachId;
		laneGeomList = lane.laneGeomList;
		laneMovements = lane.laneMovements;
		type = lane.type;
	}

	/**
	 * Add a lane node to the list of nodes.
	 * @param toAdd The node to add.
	 */
	public void addLaneNode(LaneNode toAdd) {
		laneGeomList.add(toAdd);
	}

	/** Checks if two lanes are equal. */
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Lane){
			Lane l = (Lane) obj;
			if(laneId != l.laneId || approachId != l.approachId || !type.equals(l.type)) {
				return false;
			}

			if(laneGeomList.size() != l.laneGeomList.size()) {
				return false;
			}

			Set<Integer> thisKeys = laneMovements.keySet();
			Set<Integer> lKeys = l.laneMovements.keySet();

			if(!(thisKeys.containsAll(lKeys) && lKeys.containsAll(thisKeys))) {
				return false;
			}

			for(int i = 0; i < laneGeomList.size(); i++) {

				if(!laneGeomList.contains(l.laneGeomList.get(i))) {
					return false;
				}
			}

			for(Integer i:lKeys) {
				if(!laneMovements.get(i).equals(l.laneMovements.get(i))) {
					return false;
				}
			}

			return true;
		} else {
			return false;
		}

	}
	
	/**
	 * HashCode operation. 
	 * @return A hashcode of the data. 
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(27, 5).
				append(laneId).
				append(approachId).
				append(type).
				toHashCode();
	}
	
	/**
	 * Gets the approach ID.
	 * @return The approach ID.
	 */
	public int getApproachId() {
		return approachId;
	}

	/**
	 * Gets the lane geometry list.
	 * @return The lane geometry list.
	 */
	public List<LaneNode> getLaneGeomList() {
		return laneGeomList;
	}

	/**
	 * Gets the lane ID.
	 * @return The lane ID.
	 */
	public int getLaneId() {
		return laneId;
	}

	/**
	 * Gets the lane movements.
	 * @return The map of lane movements.
	 */
	public Map<Integer, LaneMovement> getLaneMovements() {
		return laneMovements;
	}

	/**
	 * Gets the speed limit of the lane (m/s).
	 * @return The speed limit in m/s.
	 */
	public double getSpeedLimitInMetersPerSecond() {
		return speedLimitInMetersPerSecond;
	}

	/**
	 * Gets the type of lane.
	 * @return The type of lane.
	 */
	public String getType() {
		return type;
	}

	/**
	 * Sets the approach ID.
	 * @param approachId The new approach ID.
	 */
	public void setApproachId(int approachId) {
		this.approachId = approachId;
	}

	/**
	 * Sets the lane geometry list.
	 * @param laneGeomList The new lane geometry list.
	 */
	public void setLaneGeomList(List<LaneNode> laneGeomList) {
		this.laneGeomList = laneGeomList;
	}

	/**
	 * Sets the lane ID.
	 * @param laneId The new lane ID.
	 */
	public void setLaneId(int laneId) {
		this.laneId = laneId;
	}

	/**
	 * Sets the lane movements.
	 * @param laneMovements The new lane movement map.
	 */
	public void setLaneMovements(Map<Integer, LaneMovement> laneMovements) {
		this.laneMovements = laneMovements;
	}

	/**
	 * Sets the speed limit of the lane (m/s).
	 * @param speedLimitInMetersPerSecond
	 */
	public void setSpeedLimitInMetersPerSecond(double speedLimitInMetersPerSecond) {
		this.speedLimitInMetersPerSecond = speedLimitInMetersPerSecond;
	}

	/**
	 * Sets the type of lane.
	 * @param type The new type of lane.
	 */
	public void setType(String type) {
		this.type = type;
	}

	/** String representation of a lane. */
	@Override
	public String toString() {
		StringBuilder ret = new StringBuilder();

		UtilsStringOnModel.addInt(ret, laneId, "lane id");
		UtilsStringOnModel.addInt(ret, approachId, "approach id");
		UtilsStringOnModel.addList(ret, laneGeomList, "lane geom list");
		UtilsStringOnModel.addMap(ret, laneMovements, "lane movements");
		UtilsStringOnModel.addString(ret, type, "type");

		return ret.toString();
	}

	@Override
	public double getX() {
		return laneGeomList.get(0).getX();
	}

	@Override
	public double getY() {
		return laneGeomList.get(0).getY();
	}
}